package lol.clann.bukkit.pluginboot.util;

import lol.clann.clannboot.util.clazz.ClassUtils;
import lol.clann.clannboot.util.clazz.WrapField;
import lol.clann.clannboot.util.clazz.WrapMethod;
import lol.clann.clannboot.util.collection.CollectionUtils;
import net.minecraft.world.WorldServer;

import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.util.*;
import java.util.stream.Collectors;

/**
 * 开发者工具
 *
 * @author pyz
 * @date 2018/8/7 下午9:13
 */
public class WrapGenerator {

    Class clazz = WorldServer.class;
    List<String[]> fields = new ArrayList<>();
    List<String[]> methods = new ArrayList<>();

    private WrapGenerator() throws Exception {
        List<WrapField> fs = ClassUtils.getAllFields(clazz, wf -> !wf.isStatic()).collect(Collectors.toList());
        List<WrapMethod<Object>> ms = ClassUtils.getAllMethods(clazz, m -> !m.isAbstrct() && m.isPublic()).collect(Collectors.toList());
        StringBuilder sb = new StringBuilder();
        sb.append("public WrapClass<" + getFullName(clazz) + "> " + getClassFieldName() + ";");

        for (WrapField objectWrapField : fs) {
            buildField(objectWrapField);
        }
        for (WrapMethod<Object> m : ms) {
            buildMethod(m);
        }
        Set<String> msName = methods.stream().map(ss -> ss[0]).collect(Collectors.toSet());
        CollectionUtils.removeIf(fields, ss -> msName.contains(ss[0]));
        fields.sort(Comparator.comparing(s -> s[0]));
        methods.sort(Comparator.comparing(s -> s[0]));
        for (String[] ss : fields) {
            sb.append(ss[1]);
        }
        for (String[] ss : methods) {
            sb.append(ss[1]);
        }
        sb.append("@PostInstance\n" +
                "    private void init() {");
        sb.append(String.format(getClassFieldName() + " = ClassUtils.wrap(%s.class);", getFullName(clazz)));
        for (String[] ss : fields) {
            sb.append(ss[2]);
        }
        for (String[] ss : methods) {
            sb.append(ss[2]);
        }

        sb.append("}");
        for (String[] ss : methods) {
            sb.append(ss[3]);
        }
        System.out.println(sb.toString());
    }

    public static void main(String[] args) throws Exception {
        new WrapGenerator();
    }

    private String getClassFieldName() {
        return "clazz" + clazz.getSimpleName();
    }

    private void buildMethod(WrapMethod<Object> wm) {
        String define = "private WrapMethod";
        Class<?> returnType = wm.getHandle().getReturnType();
        if (!ClassUtils.isVoid(returnType)) {
            if (returnType.isPrimitive()) {
                returnType = ClassUtils.wrapPrimitive(returnType);
            }
            define = define + "<" + getClassName(returnType) + ">";
        }
        define += " " + wm.getName() + ";";

        StringJoiner classes = new StringJoiner(",");
        for (Class<?> pClass : wm.getHandle().getParameterTypes()) {
            classes.add(getFullName(pClass) + ".class");
        }

        String genCode = String.format("%s = %s.getMethod(MethodCondition.of(\"%s\", new Class[]{%s}));", wm.getName(), getClassFieldName(), wm.getName(), classes);

        StringJoiner params = new StringJoiner(",");
        if (!wm.isStatic()) {
            params.add(getClassName(wm.getHandle().getDeclaringClass()) + " handle");
        }
        int i = 0;
        for (Class parameter : wm.getHandle().getParameterTypes()) {
            params.add(getFullName(parameter) + " arg" + i);
            i++;
        }
        StringJoiner invokeParams = new StringJoiner(",");
        if (!wm.isStatic()) {
            invokeParams.add("handle");
        } else {
            invokeParams.add("null");
        }
        i = 0;
        for (Type parameter : wm.getHandle().getGenericParameterTypes()) {
            invokeParams.add("arg" + i);
            i++;
        }
        String methodInvoke = String.format("%s %s %s(%s){%s %s.invoke(%s);}",
                ClassUtils.getModifiersString(wm.getHandle()),
                getClassName(returnType),
                wm.getName(),
                params.toString(),
                ClassUtils.isVoid(returnType) ? "" : "return",
                wm.getName(),
                invokeParams.toString());

        methods.add(new String[]{wm.getName(), define, genCode, methodInvoke});
    }

    private String getSimpleName(Type type) {
        if (type instanceof ParameterizedType) {
            return ((Class) ((ParameterizedType) type).getRawType()).getSimpleName();
        } else {
            return ((Class) type).getSimpleName();
        }
    }

    private String getClassName(Class clazz) {
        if (ClassUtils.isVoid(clazz)) {
            return "void";
        }
        return getFullName(clazz);
    }

    private void buildField(WrapField<Object> wf) {
        String define = "public WrapField<%s> %s;";
        String type;
        if (wf.getType().isPrimitive()) {
            type = ClassUtils.wrapPrimitive(wf.getType()).getSimpleName();
        } else {
            type = getFullName(wf.getType());
        }
        String name = wf.getName();
        define = String.format(define, type, name);
        String genCode = String.format("%s = %s.getField(\"%s\");", wf.getName(), getClassFieldName(), wf.getName());
        fields.add(new String[]{wf.getName(), define, genCode});
    }

    private String getFullName(Class clazz) {
        if (clazz.getName().startsWith("net.minecraft.")) {
            return "net.minecraft.server.v1_7_R4." + clazz.getSimpleName();
        } else {
            return clazz.isArray() ? clazz.getSimpleName() : clazz.getName();
        }
    }

}
